/**
 * Copyright (C) 2014-2015 LinkedIn Corp. (pinot-core@linkedin.com)
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *         http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
package com.linkedin.pinot.tools.admin.command;

import com.linkedin.pinot.common.utils.NetUtil;
import com.linkedin.pinot.controller.ControllerConf;
import com.linkedin.pinot.controller.ControllerStarter;
import com.linkedin.pinot.tools.Command;
import java.io.File;
import org.apache.commons.configuration.ConfigurationException;
import org.kohsuke.args4j.Option;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;


/**
 * Class to implement StartController command.
 *
 */
public class StartControllerCommand extends AbstractBaseAdminCommand implements Command {
  private static final Logger LOGGER = LoggerFactory.getLogger(StartControllerCommand.class);

  @Option(name = "-controllerHost", required = false, metaVar = "<String>", usage = "host name for controller.")
  private String _controllerHost;

  @Option(name = "-controllerPort", required = false, metaVar = "<int>",
      usage = "Port number to start the controller at.")
  private String _controllerPort = DEFAULT_CONTROLLER_PORT;

  @Option(name = "-dataDir", required = false, metaVar = "<string>", usage = "Path to directory containging data.")
  private String _dataDir = TMP_DIR + "PinotController";

  @Option(name = "-zkAddress", required = false, metaVar = "<http>", usage = "Http address of Zookeeper.")
  private String _zkAddress = DEFAULT_ZK_ADDRESS;

  @Option(name = "-clusterName", required = false, metaVar = "<String>", usage = "Pinot cluster name.")
  private String _clusterName = DEFAULT_CLUSTER_NAME;

  @Option(name = "-configFileName", required = false, metaVar = "<FilePathName>",
      usage = "Controller Starter config file",
      forbids = { "-controllerHost", "-controllerPort", "-dataDir", "-zkAddress", "-clusterName" })
  private String _configFileName;

  @Option(name = "-help", required = false, help = true, aliases = { "-h", "--h", "--help" },
      usage = "Print this message.")
  private boolean _help = false;

  // This can be set via the set method, or via config file input.
  private boolean _tenantIsolation = true;

  @Override
  public boolean getHelp() {
    return _help;
  }

  public StartControllerCommand setControllerPort(String controllerPort) {
    _controllerPort = controllerPort;
    return this;
  }

  public StartControllerCommand setDataDir(String dataDir) {
    _dataDir = dataDir;
    return this;
  }

  public StartControllerCommand setZkAddress(String zkAddress) {
    _zkAddress = zkAddress;
    return this;
  }

  public StartControllerCommand setTenantIsolation(boolean tenantIsolation) {
    _tenantIsolation = tenantIsolation;
    return this;
  }

  public StartControllerCommand setConfigFileName(String configFileName) {
    _configFileName = configFileName;
    return this;
  }

  public StartControllerCommand setClusterName(String clusterName) {
    _clusterName = clusterName;
    return this;
  }

  @Override
  public String getName() {
    return "StartController";
  }

  @Override
  public String toString() {
    if (_configFileName != null) {
      return ("StartController -configFileName " + _configFileName);
    } else {
      return ("StartController -clusterName " + _clusterName + " -controllerHost " + _controllerHost
          + " -controllerPort " + _controllerPort + " -dataDir " + _dataDir + " -zkAddress " + _zkAddress);
    }
  }

  @Override
  public void cleanup() {

  }

  @Override
  public String description() {
    return "Start the Pinot Controller Process at the specified port.";
  }

  @Override
  public boolean execute() throws Exception {
    if (_controllerHost == null) {
      _controllerHost = NetUtil.getHostAddress();
    }

    ControllerConf conf = readConfigFromFile(_configFileName);
    if (conf == null) {
      if (_configFileName != null) {
        LOGGER.error("Error: Unable to find file {}.", _configFileName);
        return false;
      }

      conf = new ControllerConf();

      conf.setControllerHost(_controllerHost);
      conf.setControllerPort(_controllerPort);
      conf.setDataDir(_dataDir);
      conf.setZkStr(_zkAddress);

      conf.setHelixClusterName(_clusterName);
      conf.setControllerVipHost(_controllerHost);
      conf.setTenantIsolationEnabled(_tenantIsolation);

      conf.setRetentionControllerFrequencyInSeconds(3600 * 6);
      conf.setValidationControllerFrequencyInSeconds(3600);
    }

    LOGGER.info("Executing command: " + toString());
    final ControllerStarter starter = new ControllerStarter(conf);

    starter.start();

    String pidFile = ".pinotAdminController-" + String.valueOf(System.currentTimeMillis()) + ".pid";
    savePID(System.getProperty("java.io.tmpdir") + File.separator + pidFile);
    return true;
  }

  @Override
  ControllerConf readConfigFromFile(String configFileName) throws ConfigurationException {
    ControllerConf conf = null;

    if (configFileName == null) {
      return null;
    }

    File configFile = new File(_configFileName);
    if (!configFile.exists()) {
      return null;
    }

    conf = new ControllerConf(configFile);
    return (validateConfig(conf)) ? conf : null;
  }

  private boolean validateConfig(ControllerConf conf) {
    if (conf == null) {
      LOGGER.error("Error: Null conf object.");
      return false;
    }

    if (conf.getControllerHost() == null) {
      LOGGER.error("Error: missing hostname, please specify 'controller.host' property in config file.");
      return false;
    }

    if (conf.getControllerPort() == null) {
      LOGGER.error("Error: missing controller port, please specify 'controller.port' property in config file.");
      return false;
    }

    if (conf.getZkStr() == null) {
      LOGGER.error("Error: missing Zookeeper address, please specify 'controller.zk.str' property in config file.");
      return false;
    }

    if (conf.getHelixClusterName() == null) {
      LOGGER.error(
          "Error: missing helix cluster name, please specify 'controller.helix.cluster.name' property in config file.");
      return false;
    }

    return true;
  }
}
